Skip to content

MSPCA-11 Add POST /recommendations endpoint - #4

Open
Juwang110 wants to merge 7 commits into
mainfrom
jw/mspca-11-recommendations-endpoint
Open

Juwang110 wants to merge 7 commits into
mainfrom
jw/mspca-11-recommendations-endpoint

Conversation

@Juwang110

@Juwang110 Juwang110 commented Sep 27, 2026 •

Copy link
Copy Markdown

ℹ️ Issue

Closes MSPCA-11

📝 Description

Adds POST /api/recommendations, which lets a foster coordinator recommend a Chameleon animal to a volunteer before a formal match exists. The body is { volunteerId, chameleonAnimalId }, and the endpoint returns 200 with the saved recommendation (isActive: true). It returns 400 when either ID is missing or isn't a positive integer, and 404 when the volunteer doesn't exist.

Changes:

  1. Added CreateRecommendationDTO, RecommendationsService.create, and the controller route. The route uses @HttpCode(200) because the ticket asks for 200 and Nest's default for POST is 201.
  2. Added VolunteersService.existsById for the 404 check. RecommendationsModule now imports VolunteersModule, and it's registered in AppModule.
  3. create upserts on the (volunteer_id, chameleon_animal_id) primary key. Recommending the same animal twice reactivates the existing row instead of failing with a PK violation, even when two coordinators do it at the same time.
  4. validateId now uses Number.isInteger, so 1.5 and undefined are rejected.
  5. VolunteersModule now imports CoordinatorsModule. Without it, registering FosterVolunteer through autoLoadEntities made the app fail on boot with "Entity metadata for FosterVolunteer#assignedCoordinator was not found".

✔️ Verification

  • yarn test: all backend suites pass (20 suites, 160 tests). This covers new service and controller tests for success, the 404 case, each 400 case, and the 200 status code.
  • yarn lint:check, yarn format:check, and tsc --noEmit on the backend are clean.
  • To check by hand, start the backend and run:
    curl -i -X POST localhost:3000/api/recommendations \
      -H 'Content-Type: application/json' \
      -d '{"volunteerId": 1, "chameleonAnimalId": 42}'
    This should return 200 with isActive: true. A volunteer ID that doesn't exist should return 404, and a missing or non-integer ID should return 400.

🏕️ (Optional) Future Work / Notes

Juwang110 and others added 2 commits September 24, 2026 22:05
Lets foster coordinators recommend a Chameleon animal to a volunteer
before a formal match exists. New recommendations default to isActive:
true and the endpoint returns 200 with the created row.

- CreateRecommendationDTO validates volunteerId and chameleonAnimalId as
  positive integers; the controller re-checks with validateId so the
  guard holds when the handler is called directly
- 404 when the volunteer does not exist, via VolunteersService.existsById
- validateId now rejects non-integers, which the "valid integer" rule needs
- Registers RecommendationsModule on the app module

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code review caught that registering RecommendationsModule made the app
fail to boot. It pulls in VolunteersModule, the first forFeature module
reachable from AppModule, so autoLoadEntities handed TypeORM
FosterVolunteer without FosterCoordinator - which its assignedCoordinator
relation targets - and metadata building threw before app.listen. No test
caught it because none stand up the module graph.

- VolunteersModule now imports CoordinatorsModule, so it is self-contained
  wherever it is registered rather than relying on the app module
- recommendations.module.spec asserts the entity graph reachable from the
  module is closed under relations; it fails without the fix above
- Service upserts on the composite key instead of save(), so two
  coordinators recommending the same animal at once cannot race into a
  primary key violation surfacing as a 500
- Pin the 200 status the ticket requires, so dropping @httpcode fails

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Juwang110 Juwang110 changed the title Add POST /recommendations endpoint MSPCA-11 Add POST /recommendations endpoint Sep 27, 2026
justin-wang110 and others added 3 commits September 27, 2026 13:38
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Juwang110
Juwang110 marked this pull request as ready for review September 27, 2026 17:44
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@dburkhart07 dburkhart07 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some initial things but looks really good so far. learned a thing or to about repo commands from this one 🐢

Comment thread apps/backend/src/volunteers/volunteers.service.ts Outdated
Comment thread apps/backend/src/utils/validation.utils.ts
Comment thread apps/backend/src/strategies/plural-naming.strategy.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.controller.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.controller.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.controller.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.controller.spec.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.controller.spec.ts Outdated
Comment thread apps/backend/src/recommendations/recommendations.service.ts
Comment thread apps/backend/src/recommendations/recommendations.controller.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
// find(email: string) {
// return this.repo.find({ where: { email } });
// }
async findActiveOrFail(id: number): Promise<FosterVolunteer> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea to return the volunteer here. we can reuse this in most functions instead of having to do a separate DB fetch

it('throws BadRequestException when the volunteer is not active', async () => {
repo.findOneBy.mockResolvedValue({ volunteerId: 7, active: false });

await expect(service.findActiveOrFail(7)).rejects.toThrow(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make sure that findOneBy was still called?

expect(recommendationsService.create).toHaveBeenCalledWith(body);
});

it.each([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think all the tests below are covered by the dto decorators that we put on, so i think these can be removed.

});

describe('create', () => {
it('upserts the recommendation as active on the composite key', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add one more test to see the a recommendation this is inactive, when called with same volunteerId and chameleonAnimalId gets set to true

summary: 'Recommend a Chameleon Animal to an active Volunteer',
})
@ApiResponse({
status: HttpStatus.CREATED,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this endpoint be returning 200 OK instead of Nest’s default 201 Created? Since the ticket calls for POST /api/recommendations to return 200 with the saved recommendation

@dburkhart07 dburkhart07 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we also document all service functions using the following docstring format:

  • Summary line describing the function
  • Additional context about behavior or edge cases
  • @PARAM tags for parameters
  • @return tags for return value
  • @throw tags for any exceptions they throw

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants